<template>
  <div class="choose">
    <div class="canvas blur" ref="canvasBack"></div>
    <div class="canvas" ref="canvasFront" @touchmove="touch" @touchend="touch" @click="touch">
    <div class="txt">选择球页面</div>
  </div>
  </div>
</template>
<script lang="ts">
import { Vue,  Options, Emit, Inject, Prop, Watch } from 'vue-property-decorator';
import * as THREE from 'three';
// ??
import { ShaderMaterialParameters } from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import earth from '../libs/earth.json';
import { wuweiPromise, WujiInfo } from '../libs/wuwei';
// 渲染器
const rendererFront = new THREE.WebGLRenderer({
  alpha: true,
  antialias: true,
});
// 定义是否检查材质着色器程序 编译和链接过程中的错误
rendererFront.debug.checkShaderErrors = false;
rendererFront.setClearColor(new THREE.Color(), 0);
rendererFront.setPixelRatio(750 / window.innerWidth);
const rendererBack = new THREE.WebGLRenderer({
  alpha: true,
  antialias: true,
});
rendererBack.debug.checkShaderErrors = false;
rendererBack.setClearColor(new THREE.Color(), 0);

// 相机
const camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.1, 1000);
// 轨道控制器可以使得相机围绕目标进行轨道运动。 .domElement : DOMElement 一个canvas，渲染器在其上绘制输出。
const controls = new OrbitControls(camera, rendererFront.domElement);
// 将autoRotate设为true，以自动围绕目标旋转
controls.autoRotate = true;
controls.autoRotateSpeed = 0.3;
// 将enableDamping设置为true以启用阻尼（惯性），这将给控制器带来重量感。
controls.enableDamping  = true;
// 禁用摄像机平移
controls.enablePan = false;
// 禁用摄像机缩放
controls.enableZoom = false;
camera.position.set(0, 0, 2.8);
// 该值在动画循环中也必须被调用
controls.update();

// 场景
const scene = new THREE.Scene();
scene.rotation.x = Math.PI / 2;
// ？？？
const earthList: THREE.Mesh[] = [];
let earthSelect = -1;

interface PhotoMaterialParameters extends ShaderMaterialParameters{
  map?: THREE.Texture
}
// 纹理贴图
class PhotoMaterial  extends THREE.ShaderMaterial {
  public customProgramCacheKey() {
    return 'PhotoMaterial';
  }
  constructor(params: PhotoMaterialParameters = {}) {
    super({
      uniforms: {
        frontFace: { value: false },
        uvMap: { value: new THREE.Vector2(1, 1) },
        map: { value: params.map },
      },
      vertexShader: `
        varying vec2 vUv;
        void main(){
          vUv = uv.yx;
          vUv.y = 1. - vUv.y;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
        }
      `,
      fragmentShader: `
        varying vec2 vUv;
        uniform sampler2D map;
        uniform bool frontFace;
        uniform vec2 uvMap;
        void main(){
          gl_FragColor = vec4(1.);
          const float min = .05;
          const float max = 1. - min;
          if(vUv.x > min&&vUv.x<max&&vUv.y>min&&vUv.y<max){
            vec2 uv =  (vUv*(1.+min*2.)-min+(uvMap - vec2(1))/2.)/uvMap;
            gl_FragColor = texture2D(map,uv);
            gl_FragColor = mix(vec4(1),vec4(gl_FragColor.rgb,1),gl_FragColor.a);
          }
          if(!frontFace){
            gl_FragColor.rgb = mix(gl_FragColor.rgb,vec3(.05,.125,.321),.7);
          }
        }
      `,
    });
  }
}

wuweiPromise.then((wuwei) => {
  const wuweiKeys = Object.keys(wuwei);
  // 创建earth照片墙
  const transitonList: Function[] = [];
  // onBeforeRender一个可选的回调函数，在Object3D渲染之前直接执行
  // Proxy 对象用于创建一个对象的代理，从而实现基本操作的拦截和自定义（如属性查找、赋值、枚举、函数调用等）。
  scene.onBeforeRender = new Proxy(scene.onBeforeRender, {
    apply(target, thisArg, argArray) {
      for (let i = 0;i < transitonList.length;i++) {
        if (transitonList[i]()) {
          transitonList.splice(i, 1);
          i -= 1;
        }
      }
      // @ts-ignore
      target.apply(thisArg, argArray);
    },
  });
  const geometry = new THREE.PlaneGeometry(.27, .27);
  const materialCache = new PhotoMaterial();
  earth.forEach(({ matrix }, index) => {
    const material = materialCache.clone();
    if (wuweiKeys[index] !== undefined) {
      new THREE.TextureLoader().load(wuwei[wuweiKeys[index]].workMin, (texture) => {
        // 这个值定义了纹理贴图在水平方向上将如何包裹，在UV映射中对应于U。
        // 默认值是THREE.ClampToEdgeWrapping，即纹理边缘将被推到外部边缘的纹素
        texture.wrapS = THREE.ClampToEdgeWrapping;
        texture.wrapT = THREE.ClampToEdgeWrapping;
        // 缩小滤镜,它们定义了当被纹理化的像素映射到大于1纹理元素（texel）的区域时，将要使用的纹理缩小函数。
        texture.minFilter =  THREE.NearestFilter;
        texture.magFilter = THREE.LinearFilter;
        const min = Math.min(texture.image.naturalHeight, texture.image.naturalWidth);
        Promise.resolve().then(() => {
          material.uniforms.map.value = texture;
        });
        material.uniforms.uvMap.value.set(
          texture.image.naturalWidth / min,
          texture.image.naturalHeight / min,
        );
      });
    }
    const plane = new THREE.Mesh(geometry, material);
    plane.name = `photo_${index}`;
    // transpose 转置函数
    // @ts-ignore
    const matrixTo = new THREE.Matrix4().set(...matrix)
      .transpose();
    // 从变换矩阵（transformation matrix）m中， 设置该向量为其中与位置相关的元素。
    const positionTo = new THREE.Vector3().setFromMatrixPosition(matrixTo);
    const positionStart = new THREE.Vector3(
      (Math.random() - .5) * 100,
      (Math.random() - .5) * 100,
      (Math.random() - .5) * 100,
    );
    let time = Math.random() * .25 + .6;
    if (index === 0) {
      time = .6;
    }
    transitonList.push(() => {
      if (time < 1) {
        time += 0.001;
        const progress = Math.sin(Math.max(0, time) * Math.PI / 2) ** 4;
        if (index === 0) {
          scene.rotation.z = Math.PI / progress;
        }
        plane.position.x = positionStart.x + progress * (positionTo.x - positionStart.x);
        plane.position.y = positionStart.y + progress * (positionTo.y - positionStart.y);
        plane.position.z = positionStart.z + progress * (positionTo.z - positionStart.z);
      } else {
        plane.position.copy(positionTo);
        return true;
      }
      return false;
    });
    plane.onBeforeRender =  new Proxy(plane.onBeforeRender, {
      apply(target, thisArg, argArray) {
        if (argArray[0] === rendererFront) {
          (material as PhotoMaterial).uniforms.frontFace.value = true;
          material.side = THREE.FrontSide;
        } else {
          (material as PhotoMaterial).uniforms.frontFace.value = false;
          material.side = THREE.BackSide;
        }
        if (earthSelect === index) {
          if (plane.scale.x > .7) {
            plane.scale.x = Math.max(.7, plane.scale.x - Math.max(.001, (plane.scale.x - .7) / 10));
            plane.scale.y = plane.scale.x;
            plane.scale.z = plane.scale.x;
          }
        } else {
          if (plane.scale.x < 1) {
            plane.scale.x = Math.min(1, plane.scale.x + Math.max(.001, (1 - plane.scale.x) / 10));
            plane.scale.y = plane.scale.x;
            plane.scale.z = plane.scale.x;
          }
        }
        // @ts-ignore
        target.apply(thisArg, argArray);
      },
    });
    plane.applyMatrix4(matrixTo);
    plane.scale.set(1, 1, 1);
    plane.position.copy(positionStart);
    earthList.push(plane);
    scene.add(plane);
  });
});
// 光线投射
const raycaster = new THREE.Raycaster();
const pointer = new   THREE.Vector2();
@Options({ components: {} })
export default class ChooseCircle extends Vue {
  @Inject() public wuwei!: {[key: string]: WujiInfo};

  private get wuweiKeys() {
    return Object.keys(this.wuwei);
  }
  private play() {
    rendererFront.setAnimationLoop(() => {
      controls.update();
      // 用相机(camera)渲染一个场景(scene)
      rendererBack.render(scene, camera);
      rendererFront.render(scene, camera);
    });
  }
  public mounted() {
    earthSelect = -1;
    const rect = (this.$el as HTMLElement).getBoundingClientRect();
    camera.aspect = rect.width / rect.height;
    rendererFront.setSize(rect.width, rect.height, false);
    rendererBack.setSize(rect.width / 4, rect.height / 4, false);
    (this.$refs.canvasFront as HTMLDivElement).append(rendererFront.domElement);
    (this.$refs.canvasBack as HTMLDivElement).append(rendererBack.domElement);
    this.play();
  }
  public unmounted() {
    rendererFront.setAnimationLoop(null);
  }
}
</script>
<style scoped lang="postcss">
.choose-circle{
  touch-action: none;
  position: absolute;
  left: 0%;top: 0%;
  width: 100%;height: 100%;
  .txt{
    position: absolute;
    left: 50%;top: 50%;
    width: 100%;height: 100px;
    margin-left:-50%;
    margin-top:-50px;
    color:#fff;
    text-align:center;
  }
}
</style>
